package com.ilink.ilinkcommon.log.method;

import com.ilink.ilinkcommon.utils.UserContext;
import java.util.Objects;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 使用Spring AOP实现的日志切面类，
 * 用于在方法执行后记录日志
 * @author dzy
 */
@Slf4j
@Component
@Aspect
public class MethodLogAspect {

    /**
     * 定义一个切点，该切点匹配所有被@MethodLog注解标记的方法
     */
    @Pointcut("@annotation(com.ilink.ilinkcommon.log.method.MethodLog)")
    public void methodLog(){}

    /**
     * 在方法执行之后记录日志的切面方法
     * @param joinPoint 切入点，用于获取连接点（目标方法）的相关信息
     */
    @After("methodLog()")
    public void logAfter(JoinPoint joinPoint) {  //在加了MethodLog注解的方法执行后执行(写日志)
        // 尝试获取请求属性
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();

        // 如果当前线程没有与之关联的请求属性，那么可能是异步任务或非HTTP请求处理线程
        if (requestAttributes == null) {
            log.warn("No request attributes found, possibly running in a non-HTTP context.");
            return; // 退出方法，不记录涉及HTTP请求信息的日志
        }

        HttpServletRequest request = requestAttributes.getRequest();
        String ipAddress = this.getIpAddr(request);
        String userId = UserContext.getUserId();
        Date date = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formattedDate = formatter.format(date);
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        //获取类级别的@RequestMapping注解的值
        RequestMapping classRequestMapping = AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), RequestMapping.class);
        String classPath = (classRequestMapping != null) ? String.join("/", classRequestMapping.value()) : "";
        //获取方法级别的@RequestMapping注解的值
        RequestMapping methodRequestMapping = AnnotatedElementUtils.findMergedAnnotation(method, RequestMapping.class);
        String methodPath = (methodRequestMapping != null) ? String.join("/", methodRequestMapping.value()) : "";
        //构建完整的接口路由
        String fullPath = (classPath + "/" + methodPath).replaceAll("/{2,}", "/");
        //获取方法上的MethodLog注解中value字段的值
        MethodLog methodLog = method.getAnnotation(MethodLog.class);
        String logMessage = (methodLog != null) ? methodLog.value() : "";
        if(logMessage.isEmpty()) {
            log.info("用户id:{} 用户IP地址:{} 时间:{} 访问接口路由:{}", userId, ipAddress, formattedDate, fullPath);
        } else {
            log.info("用户id:{} 用户IP地址:{} 时间:{} 访问接口路由:{} 用户行为:{}", userId, ipAddress, formattedDate, fullPath, logMessage);
        }
    }

    /**
     * 获取客户端IP地址
     * @param request HttpServletRequest对象，用于获取请求头信息
     * @return 客户端IP地址
     */
    // note: 在 HTTP 请求处理线程中使用时是线程安全的，不会引起线程安全问题。
    // 但请避免在异步任务、非请求线程或定时任务中直接调用。
    // 对于异步处理场景，建议将 HttpServletRequest 或其相关信息（如 IP 地址）作为参数传递，确保数据的一致性和安全性。
    private String getIpAddr(HttpServletRequest request) {
        String ipAddress = request.getHeader("X-Forwarded-For");
        if(ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress))  //尝试从Proxy-Client-IP头中获取
            ipAddress = request.getHeader("Proxy-Client-IP");
        if(ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress))  //尝试从WL-Proxy-Client-IP头中获取
            ipAddress = request.getHeader("WL-Proxy-Client-IP");
        if(ipAddress == null || ipAddress.isEmpty() || "unknown".equalsIgnoreCase(ipAddress)){  //如果以上都没有取到，则使用request.getRemoteAddr()
            ipAddress = request.getRemoteAddr();
            if ("127.0.0.1".equals(ipAddress)) {
                try {
                    InetAddress inet = InetAddress.getLocalHost();
                    ipAddress = inet.getHostAddress();
                } catch (UnknownHostException e) {
                    log.error("Error retrieving IP address", e);
                }
            }
        }
        if(ipAddress != null && ipAddress.contains(","))  //如果IP地址中有逗号，说明可能经过多级代理，取第一个IP地址
            ipAddress = ipAddress.split(",")[0];
        return ipAddress;
    }

}
